/******************************************************************************
	Cobalt - utility libraries for building XNA games
	Copyright (C) 2008 Philip Djonov

	This program is free software; you can redistribute it and/or modify it
	under the terms of the GNU General Public License as published by the Free
	Software Foundation; either version 2 of the License, or (at your option)
	any later version.

	This program is distributed in the hope that it will be useful, but
	WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
	or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
	for more details.

	You should have received a copy of the GNU General Public License along
	with this program; if not, write to the
	
		Free Software Foundation, Inc.
		51 Franklin Street, Fifth Floor
		Boston, MA  02110-1301, USA.
******************************************************************************/

using System;
using System.Collections.Generic;
using System.IO;
using System.Globalization;
using System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace Cobalt.Content.Pipeline
{

	public class EffectWriter
	{
		private StringBuilder fxText = new StringBuilder();
		private bool onNewLine = true;
		private bool wantsNewLine = false;
		private int indent;

		public void Clear()
		{
			fxText.Length = 0;

			onNewLine = true;
			wantsNewLine = false;
			indent = 0;
		}

		public EffectWriter Clone()
		{
			EffectWriter ret = new EffectWriter();
			ret.Write( this );
			return ret;
		}

		public void Write( string format, params object[] args )
		{
			string text = Format( format, args );
			Append( text );
		}

		public void WriteLine( string format, params object[] args )
		{
			string text = Format( format, args );
			Append( text );
			AppendNewLine();
		}

		public void WriteLine()
		{
			AppendNewLine();
		}

		public void Write( EffectWriter writer )
		{
			Append( writer.ToString() );
		}

		#region Formatting
		protected class Formatter : IFormatProvider, ICustomFormatter
		{

			private NumberFormatInfo numberFormat = (NumberFormatInfo)CultureInfo.InvariantCulture.NumberFormat.Clone();
			public NumberFormatInfo NumberFormat { get { return numberFormat; } }

			protected static readonly string[] BlendNames = new string[]
			{
				"!!Invalid!!",
				"ZERO", "ONE",
				"SRCCOLOR", "INVSRCCOLOR",
				"SRCALPHA", "INVSRCALPHA",
				"DESTALPHA", "INVDESTALPHA",
				"DESTCOLOR", "INVDESTCOLOR",
				"SRCALPHASAT",
				"BOTHSRCALPHA", "BOTHINVSRCALPHA",
				"BLENDFACTOR", "INVBLENDFACTOR",
				"SRCCOLOR2", "INVSRCCOLOR2",
			};

			protected static readonly string[] CullNames = new string[]
			{
				"!!Invalid!!",
				"NONE",
				"CW", "CCW",
			};

			protected static readonly string[] CompareNames = new string[]
			{
				"!!Invalid!!",
				"NEVER",
				"LESS",
				"EQUAL",
				"LESSEQUAL",
				"GREATER",
				"NOTEQUAL",
				"GREATEREQUAL",
				"ALWAYS",
			};

			protected static readonly string[] TextureAddressNames = new string[]
			{
				"!!Invalid!!",
				"WRAP",
				"MIRROR",
				"CLAMP",
				"BORDER",
				"MIRRORONCE",
			};

			protected static readonly string[] TextureFilterNames = new string[]
			{
				"NONE",
				"POINT",
				"LINEAR",
				"ANISOTROPIC",
				"PYRAMIDALQUAD",
				"GAUSSIANQUAD",
				"CONVOLUTIONMONO",
			};

			public string Format( string format, object arg, IFormatProvider formatProvider )
			{
				if( arg == null )
					return "<<<null>>>";

				if( arg is Color )
				{
					Color cl = (Color)arg;

					switch( format )
					{
					case "a":
						arg = (float)cl.A / 0xFF;
						break;

					case "rgb":
						arg = cl.ToVector3();
						break;

					case "rgba":
					default:
						arg = cl.ToVector4();
						break;
					}
				}

				if( arg is Vector2 )
				{
					Vector2 v = (Vector2)arg;
					return string.Format( "float2( {0}, {1} )", v.X, v.Y );
				}

				if( arg is Vector3 )
				{
					Vector3 v = (Vector3)arg;
					return string.Format( "float3( {0}, {1}, {2} )", v.X, v.Y, v.Z );
				}

				if( arg is Vector4 )
				{
					Vector4 v = (Vector4)arg;
					return string.Format( "float4( {0}, {1}, {2}, {3} )", v.X, v.Y, v.Z, v.W );
				}

				if( arg is Blend )
					return BlendNames[(int)(Blend)arg];

				if( arg is CullMode )
					return CullNames[(int)(CullMode)arg];

				if( arg is CompareFunction )
					return CompareNames[(int)(CompareFunction)arg];

				if( arg is TextureAddressMode )
					return TextureAddressNames[(int)(TextureAddressMode)arg];

				if( arg is TextureFilter )
					return TextureFilterNames[(int)(TextureFilter)arg];

				IFormattable argFmat = arg as IFormattable;
				if( argFmat != null )
					return argFmat.ToString( format, formatProvider );

				return arg.ToString();
			}

			public virtual object GetFormat( Type formatType )
			{
				if( formatType == typeof( NumberFormatInfo ) )
					return numberFormat;

				if( formatType == typeof( ICustomFormatter ) )
					return this;

				return null;
			}
		}

		private Formatter formatter = new Formatter();
		protected Formatter ValueFormatter { get { return formatter; } set { formatter = value; } }
		
		private string Format( string format, params object[] args )
		{
			return string.Format( formatter, format, args );
		}
		#endregion

		public void Append( string text )
		{
			if( text == null )
				throw new ArgumentNullException( "text" );

			for( int idx = 0; idx < text.Length; idx++ )
			{
				char ch = text[idx];
				Append( ch );
			}
		}

		private void Append( char ch )
		{
			switch( ch )
			{
			case '{':
				if( !onNewLine )
					AppendNewLine();

				fxText.Append( '{' );
				indent++;

				wantsNewLine = true;
				break;

			case '}':
				if( indent == 0 )
					throw new Exception( "Mismatched closing curly brace." );

				indent--;

				if( !onNewLine )
					AppendNewLine();

				fxText.Append( '}' );
				wantsNewLine = true;
				break;

			case ';':
				fxText.Append( ';' );
				wantsNewLine = true;
				break;

			case '\n':
				AppendNewLine();
				break;

			case '\r':
				break;

			case ' ':
			case '\t':
				if( wantsNewLine || onNewLine )
					//skip white space at start of lines, indentation serves
					return;

				goto default;

			default:
				if( wantsNewLine )
					AppendNewLine();

				fxText.Append( ch );

				onNewLine = false;
				break;
			}
		}

		private void AppendNewLine()
		{
			fxText.Append( '\n' );
			for( int i = 0; i < indent; i++ )
				fxText.Append( '\t' );

			wantsNewLine = false;
			onNewLine = true;
		}

		public override string ToString()
		{
			return fxText.ToString();
		}
	}

}